package com.shiyi.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shiyi.common.Constants;
import com.shiyi.common.ResponseResult;
import com.shiyi.common.ResultCode;
import com.shiyi.dto.article.ArticleDTO;
import com.shiyi.entity.*;
import com.shiyi.enums.DataEventEnum;
import com.shiyi.enums.YesOrNoEnum;
import com.shiyi.event.DataEventPublisherService;
import com.shiyi.exception.BusinessException;
import com.shiyi.mapper.*;
import com.shiyi.service.ArticleService;
import com.shiyi.service.FileService;
import com.shiyi.utils.*;
import com.shiyi.vo.article.SystemArticleListVO;
import com.shiyi.vo.article.XkArchiveItemVO;
import com.shiyi.vo.article.XkArchiveVO;
import com.shiyi.vo.user.SystemUserVO;
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
import com.vladsch.flexmark.util.data.MutableDataSet;
import io.swagger.models.auth.In;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.shiyi.common.ResultCode.*;

/**
 * <p>
 * 博客文章表 服务实现类
 * </p>
 *
 * @author blue
 * @since 2021-08-18
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {

    private final CategoryMapper categoryMapper;

    private final TagsMapper tagsMapper;

    private final RestTemplate restTemplate;

    private final FileService fileService;


    private final DataEventPublisherService dataEventPublisherService;

    private final String BAIDU_URL = "http://data.zz.baidu.com/urls?site=www.codeutils.cn&token=qraK6eC4BFcA4SMI";


    /**
     *  后台获取所有文章
     * @return
     */
    @Override
    public ResponseResult selectArticlePage(String title,Integer tagId,Integer categoryId,Integer isPublish) {
        Page<SystemArticleListVO> data = baseMapper.selectArticle(new Page<>(PageUtil.getPageNo(), PageUtil.getPageSize()),title,tagId,categoryId,isPublish);
        return ResponseResult.success(data);
    }

    /**
     *  后台获取文章详情
     * @return
     */
    @Override
    public ResponseResult selectArticleById(Long id) {
        ArticleDTO articleDTO = baseMapper.selectArticlePrimaryKey(id);
        articleDTO.setTags(tagsMapper.selectByArticleId(id));
        return ResponseResult.success(articleDTO);
    }

    /**
     *  添加文章
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult addArticle(ArticleDTO article) {
        Article blogArticle = BeanCopyUtil.copyObject(article, Article.class);
        blogArticle.setUserId(StpUtil.getLoginIdAsString());
        //添加分类
        Long categoryId = savaCategory(article.getCategoryName());
        blogArticle.setCategoryId(categoryId);


        //添加标签
        List<Long> tagList = getOrCreateTagsList(article);

        String ipAddress = IpUtil.getIp2region(IpUtil.getIp());
        if (StringUtils.isNotBlank(ipAddress)) {
            String[] parts = ipAddress.split("\\|");
            if (parts.length > 1) {
                String address = parts[1];
                blogArticle.setAddress(address);
            }
        }
        baseMapper.insert(blogArticle);
        tagsMapper.saveArticleTags(blogArticle.getId(),tagList);

        //发布消息去同步es 不进行判断是否是发布状态了，因为后面修改成下架的话就还得去删除es里面的数据，多此一举了，在查询时添加条件发布状态为已发布
        dataEventPublisherService.publishData(DataEventEnum.ES_ADD,blogArticle);
        return ResponseResult.success();
    }

    /**
     *  修改文章
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult updateArticle(ArticleDTO article) {
        Article blogArticle = baseMapper.selectById(article.getId());
        if (ObjectUtil.isNull(blogArticle)) {
            throw new BusinessException(ResultCode.ARTICLE_NOT_EXIST);
        }
        //只能修改属于当前登录用户的文章
        String loginId = StpUtil.getLoginIdAsString();
        if (!blogArticle.getUserId().equals(loginId) && !StpUtil.hasRole(Constants.ADMIN_CODE)){
            throw new BusinessException(ResultCode.NO_PERMISSION);
        }

        //添加分类
        Long categoryId = savaCategory(article.getCategoryName());
        //添加标签
        List<Long> tagList = getOrCreateTagsList(article);

        blogArticle = BeanCopyUtil.copyObject(article, Article.class);
        blogArticle.setCategoryId(categoryId);
        baseMapper.updateById(blogArticle);

        //先删出所有标签
        tagsMapper.deleteByArticleIds(Collections.singletonList(blogArticle.getId()));
        //然后新增标签
        tagsMapper.saveArticleTags(blogArticle.getId(),tagList);

        //发布消息去同步es
        dataEventPublisherService.publishData(DataEventEnum.ES_UPDATE,blogArticle);
        return ResponseResult.success();
    }


    /**
     * 批量删除文章
     * @param ids
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult deleteBatchArticle(List<Long> ids) {
        baseMapper.deleteBatchIds(ids);
        tagsMapper.deleteByArticleIds(ids);

        dataEventPublisherService.publishData(DataEventEnum.ES_DELETE,ids);
        return ResponseResult.success();
    }

    /**
     *  置顶文章
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult topArticle(ArticleDTO article) {
        baseMapper.putTopArticle(article);
        return ResponseResult.success();
    }


    /**
     *  文章百度推送
     * @return
     */
    @Override
    public ResponseResult seoArticle(List<Long> ids) {

        // 检查边界条件
        if (ids == null || ids.isEmpty()) {
            throw new BusinessException("ids cannot be null or empty");
        }

        HttpHeaders headers = new HttpHeaders();
        headers.add("Host", "data.zz.baidu.com");
        // 动态User-Agent
        headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
                "(KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36");
        headers.setContentType(MediaType.TEXT_PLAIN);

        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        List<Future<?>> futures = ids.stream().map(item -> executor.submit(() -> {
            try {
                String url = "http://www.codeutils.cn/article/" + URLEncoder.encode(item.toString(), StandardCharsets.UTF_8.name());
                HttpEntity<String> entity = new HttpEntity<>(url, headers);
                restTemplate.postForObject(BAIDU_URL, entity, String.class);
            } catch (RestClientException | UnsupportedEncodingException e) {
                // 异常处理逻辑，例如记录日志等
                e.printStackTrace();
            }
        })).collect(Collectors.toList());

        // 等待所有任务完成
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (Exception e) {
                // 异常处理，例如记录日志
                e.printStackTrace();
            }
        }

        executor.shutdown();

        return ResponseResult.success();
    }

    /**
     *  抓取文章
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult reptile(Integer type,String url) {
        if (type == null){
            return ResponseResult.error("请选择源站");
        }
        try {
            Document document = Jsoup.connect(url).get();
            Elements title  = null;
            Elements tags = null;
            Elements content = null;
            String newContent = "";
            if (type == 1){
                title  = document.getElementsByClass("title-article");
                tags  = document.getElementsByClass("tag-link");
                content  = document.getElementsByClass("article_content");
                if (StringUtils.isBlank(content.toString())) {
                    throw new BusinessException(ResultCode.CRAWLING_ARTICLE_FAILED.getDesc());
                }
                //爬取的是HTML内容，需要转成MD格式的内容
                newContent = content.get(0).toString().replaceAll("<code>", "<code class=\"lang-java\">");
            }
            if (type == 2){
                title  = document.getElementsByClass("postTitle2 vertical-middle");
                content  = document.getElementsByClass("blogpost-body blogpost-body-html");
                String regex = "<pre(\\s[^>]*)?>";
                String newContent1 = content.get(0).toString().replaceAll(regex, "<pre> <code class=\"lang-java\">");
                String newContent2 = newContent1.replaceAll("</pre>", "</code></pre>");
                String newContent3 = newContent2.replace("<span style=\"color: rgba(0, 0, 255, 1)\">", "<span class=\"token keyword\">");
                String newContent4 = newContent3.replace("<span style=\"color: rgba(0, 0, 0, 1)\">", "<span class=\"token keyword\">");
                // 定义要匹配的行号标记的正则表达式
                // 注意在Java字符串中，反斜杠需要使用四个反斜杠来转义
                String lineNumberPattern = "<span\\s+style=\"color:\\s+rgba\\(0,\\s+128,\\s+128,\\s+1\\)\\\">\\s*[^<]+\\s*</span>";

                // 使用正则表达式替换行号标记为空字符串
                newContent = newContent4.replaceAll(lineNumberPattern, "");
            }
            MutableDataSet options = new MutableDataSet();
            String markdown = FlexmarkHtmlConverter.builder(options).build().convert(newContent)
                    .replace("lang-java","java");
            //格式化内容img图片
            newContent = formatContent(newContent);
            Article entity = Article.builder().userId(StpUtil.getLoginIdAsString()).contentMd(markdown)
                    .categoryId(17L).isOriginal(YesOrNoEnum.NO.getCode()).originalUrl(url).isPublish(1)
                    .title(title.get(0).text()).summary(title.get(0).text()).avatar(getImgStr(newContent).get(0)).content(newContent).build();

            baseMapper.insert(entity);
            if (type == 1){
                //为该文章添加标签
                List<Long> tagsId = new ArrayList<>();
                tags.forEach(item ->{
                    String tag = item.text();
                    Tags result = tagsMapper.selectOne(new LambdaQueryWrapper<Tags>().eq(Tags::getName,tag ));
                    if (result == null){
                        result = Tags.builder().name(tag).build();
                        tagsMapper.insert(result);
                    }
                    tagsId.add(result.getId());
                });
                tagsMapper.saveArticleTags(entity.getId(),tagsId);
            }else {
                //为该文章添加标签
                List<Long> tagsId = new ArrayList<>();
                String tag = "其他";
                Tags result = tagsMapper.selectOne(new LambdaQueryWrapper<Tags>().eq(Tags::getName,tag ));
                if (result == null){
                    result = Tags.builder().name(tag).build();
                    tagsMapper.insert(result);
                }
                tagsId.add(result.getId());
                tagsMapper.saveArticleTags(entity.getId(),tagsId);
            }
            log.info("文章抓取成功，内容为:{}", JSON.toJSONString(entity));
        } catch (IOException e) {
            throw new BusinessException(e);
        }
        return ResponseResult.success();
    }

    public void crawlingArticle(Integer page, Integer size,String type) throws Exception {
        //小k网
        String result = request("https://www.xkwo.com/media-all?page="+page+"&size="+size);

        XkArchiveVO vo = JSONObject.parseObject(result, XkArchiveVO.class);
        List<XkArchiveItemVO> records = vo.getData().getRecords();
        if (StringUtils.isBlank(type)){
            records = records.stream().filter(item -> isToday(item.getReleaseTime())).collect(Collectors.toList());
        }
        List<Article> articles = new ArrayList<>();
        for (XkArchiveItemVO record : records) {
            String replace = record.getThumbnail().replace("http://", "https://");
            MultipartFile file = FileUtils.downloadAsMultipartFile(replace);
            ResponseResult upload = fileService.upload(file);
            String img = (String) upload.getData();
            MutableDataSet options = new MutableDataSet();
            String content = formatContent2(record.getContent());
            String markdown = FlexmarkHtmlConverter.builder(options).build().convert(content)
                    .replace("lang-java","java");
            Article entity = Article.builder().userId("1")
                    .categoryId(13L).isOriginal(YesOrNoEnum.YES.getCode()).isPublish(1)
                    .contentMd(markdown)
                    .title(record.getArticleTitle()).summary(record.getSnopsis()).avatar(img).content(content).build();
            articles.add(entity);
            //为该文章添加标签
        }
        saveBatch(articles);
        String tag = "文章";
        Tags result1 = tagsMapper.selectOne(new LambdaQueryWrapper<Tags>().eq(Tags::getName,tag ));
        if (result1 == null){
            result1 = Tags.builder().name(tag).build();
            tagsMapper.insert(result1);
        }
        List<Long> collect = articles.stream().map(Article::getId).collect(Collectors.toList());
        tagsMapper.saveArticleTags2(collect,result1.getId());
    }


    public static boolean isToday(String dateTimeStr) {
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        // 将字符串解析为LocalDateTime对象
        LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, formatter);

        // 获取当前日期
        LocalDate today = LocalDate.now();

        // 比较给定日期是否与当前日期相同
        return dateTime.toLocalDate().equals(today);
    }

    private String request(String realUrl){
        try {
            // 传入参数
            URL url = new URL(realUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 在连接之前设置属性
            // Content-Type实体头用于向接收方指示实体的介质类型，指定HEAD方法送到接收方的实体介质类型，或GET方法发送的请求介质类型
            conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
            // 设置打开与此URLConnection引用的资源的通信链接时使用的指定超时值（以毫秒为单位）
            conn.setConnectTimeout(10000);
            // 将读取超时设置为指定的超时时间，以毫秒为单位。
            // conn.setReadTimeout(60000);
            conn.setRequestMethod("GET");
            // Post 请求不能使用缓存
            conn.setUseCaches(false);
            // 建立连接
            conn.connect();
            // 获取响应
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            String result = "";
            while ((line = reader.readLine()) != null) {
                result += line;
            }
            reader.close();
            conn.disconnect();
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public List<String> getImgStr(String htmlStr) {
        List<String> list = new ArrayList<>();
        String img = "";
        Pattern p_image;
        Matcher m_image;
        // String regEx_img = "<img.*src=(.*?)[^>]*?>"; //图片链接地址
        String regEx_img = "<img.*src\\s*=\\s*(.*?)[^>]*?>";
        p_image = Pattern.compile(regEx_img, Pattern.CASE_INSENSITIVE);
        m_image = p_image.matcher(htmlStr);
        while (m_image.find()) {
            // 得到<img />数据
            img = m_image.group();
            // 匹配<img>中的src数据
            Matcher m = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)").matcher(img);
            while (m.find()) {
                list.add(m.group(1));
            }
        }
        if (ObjectUtils.isEmpty(list)){
            list.add("https://picsum.photos/500/300");
        }
        return list;
    }

    private String formatContent2(String content) throws IOException {
        List<String> imgList = new ArrayList<>();
        try {
            // 解析HTML字符串
            Document doc = Jsoup.parse(content);
            // 使用CSS选择器选择所有的img标签
            Elements imgElements = doc.select("img");
            // 遍历所有的img标签并打印src属性值
            for (Element img : imgElements) {
                String src = img.attr("src");
                imgList.add(src);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //上传文件
        Map<String,String> urlMap = new HashMap<>();
        for (String url : imgList) {
            String replace = url.replace("http://", "https://");
            if (StringUtils.isBlank(replace)){
                replace = url;
            }
            MultipartFile file = FileUtils.downloadAsMultipartFile(replace);
            ResponseResult upload = fileService.upload(file);
            String img = (String) upload.getData();
            urlMap.put(url,img);
        }
        for (Map.Entry<String, String> entry : urlMap.entrySet()) {
            //原始url
            String key = entry.getKey();
            //新url
            String value = entry.getValue();
            content = content.replace(key, value);
        }
        return content;
    }


    private String formatContent(String content) throws IOException {
        List<String> imgList = new ArrayList<>();
        try {
            // 解析HTML字符串
            Document doc = Jsoup.parse(content);
            // 使用CSS选择器选择所有的img标签
            Elements imgElements = doc.select("img");
            // 遍历所有的img标签并打印src属性值
            for (Element img : imgElements) {
                String src = img.attr("src");
                imgList.add(src);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //上传文件
        Map<String,String> urlMap = new HashMap<>();
        for (String url : imgList) {
            MultipartFile file = FileUtils.downloadAsMultipartFile(url);
            ResponseResult upload = fileService.upload(file);
            String img = (String) upload.getData();
            urlMap.put(url,img);
        }
        for (Map.Entry<String, String> entry : urlMap.entrySet()) {
            //原始url
            String key = entry.getKey();
            //新url
            String value = entry.getValue();
            content = content.replace(key, value);
        }
        return content;
    }
    /**
     *  发布或下架文章
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseResult toggleArticlePublication(Article article) {
        baseMapper.updateById(article);
        return ResponseResult.success();
    }

    @Override
    public ResponseResult randomImg() {
        return ResponseResult.success("https://picsum.photos/500/300?random=" + System.currentTimeMillis());
    }


    /**
     * 将数据库不存在的标签新增
     * @param article
     * @return
     */
    private List<Long> getOrCreateTagsList(ArticleDTO article) {
        List<Long> tagList = new ArrayList<>();
        article.getTags().forEach(item ->{
            Tags tags = tagsMapper.selectOne(new LambdaQueryWrapper<Tags>().eq(Tags::getName, item));
            if (tags == null){
                tags = Tags.builder().name(item).sort(0).build();
                tagsMapper.insert(tags);
            }
            tagList.add(tags.getId());
        });
        return tagList;
    }

    /**
     * 如果分类不存在则新增
     * @param categoryName
     * @return
     */
    private Long savaCategory(String categoryName) {
        Category category = categoryMapper.selectOne(new LambdaQueryWrapper<Category>().eq(Category::getName, categoryName));
        if (category == null){
            category = Category.builder().name(categoryName).sort(0).build();
            categoryMapper.insert(category);
        }
        return category.getId();
    }
}
